package fag.core.geometry;

import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.io.Serializable;

import fag.core.collision.Clipping.Feature;
import fag.core.collision.Hitbox;

/**
 * Classe vecteur 2D.
 * @author lumz
 */
public class Vector2D extends Hitbox implements Cloneable, Serializable, Comparable {

	private static final long serialVersionUID = -6215075151203896778L;
	
	public double x = 0;
	public double y = 0;
	
	/**
	 * Constructeur par défault.
	 * Le vecteur est intialisé à 0, 0.
	 */
	public Vector2D() {}
	
	/**
	 * Constructeur avec initialisation.
	 * @param x Coordonnée x.
	 * @param y Coordonnée y.
	 */
	public Vector2D(double x, double y) {
		this.x = x;
		this.y = y;
	}
	
	/**
	 * Calculer la somme de deux vecteurs.
	 * @param vector Vecteur à additionner.
	 * @return La somme des deux vecteurs.
	 */
	public Vector2D add(Vector2D vector) {
		return new Vector2D(this.x + vector.x, this.y + vector.y);
	}
	
	@Override
	public Vector2D clone() {
		return (Vector2D) super.clone();
	}
	
	@Override
	public int compareTo(Object object) {
        
		Vector2D other = (Vector2D) object;
        double otherX = other.getX();
        if (this.x < otherX)  return -1;
        else if(this.x == otherX) return 0;
        else return 1;
    }
	
	/**
	 * Pseudo produit vectoriel dans le plan 2D.
	 * @param z La norme du vecteur perpendiculaire.
	 * @return Le pseudo produit vectoriel dans le plan.
	 */
	public Vector2D cross(double z) {
		return new Vector2D(-1.0 * y * z, x * z);
	}
	
	@Override
	public boolean equals(Object other) {
		
		if(this == other)
			return true;
		
		if(!(other instanceof Vector2D))
			return false;
		
		final Vector2D vector = (Vector2D)(other);
		
		return x == vector.x && y == vector.y;
	}
	
	/**
	 * Calcule le déterminant.
	 * @param vector Vecteur du déterminant.
	 * @return Le déterminant.
	 */
	public double det(Vector2D vector) {
		return this.x * vector.y - this.y * vector.x;
	}
	
	/**
	 * Calcule le produit scalaire.
	 * @param vector Vecteur du produit scalaire.
	 * @return Le produit scalaire.
	 */
	public double dot(Vector2D vector) {
		return this.x * vector.x + this.y * vector.y;
	}
	
	@Override
	public Rectangle2D getBounds() {
		return new Rectangle2D(x, y, 0, 0);
	}
	
	/**
	 * Calcule la norme.
	 * @return La norme du vecteur.
	 */
	public double length() {
		return Math.sqrt(x * x + y * y);
	}
	
	/**
	 * Calcule la multiplication par un scalaire.
	 * @param k Multiplicateur scalaire du vecteur.
	 * @return Le résultat de la multiplication scalaire.
	 */
	public Vector2D mul(double k) {
		return new Vector2D(this.x * k, this.y * k);
	}
	
	/**
	 * Normale du vecteur.
	 * @param winding Sens de la normale.
	 * @return La normale de ce vecteur dans le sens spécifié.
	 */
	public Vector2D normal(Winding winding) {
	
		switch(winding) {
		case CLOCKWISE :
			return new Vector2D(y, -x);
		default :
			return new Vector2D(-y, x);
		}
	}
	
	/**
	 * Normalisation de vecteur.
	 * @return Le résultat de la normalisation.
	 */
	public Vector2D normalize() {
		
		double length = length();
		
		return new Vector2D(this.x / length, this.y / length);
	}
	
	/**
	 * Setter du vecteur. 
	 * @param vector Le nouveau vecteur.
	 */
	public void set(Vector2D vector) {
		x = vector.x;
		y = vector.y;
	}
	
	/**
	 * Setter du vecteur.
	 * @param x La nouvelle coordonnée x.
	 * @param y La nouvelle coordonnée y.
	 */
	public void set(double x, double y) {
		this.x = x;
		this.y = y;
	}
	
	/**
	 * Accesseurs
	 */
	public double getX() { return this.x; }
    public double getY() { return this.y; }
	
	/**
	 * Soustraction de vecteurs.
	 * @param vector Le vecteur à soustraire.
	 * @return La soustraction des deux vecteurs.
	 */
	public Vector2D sub(Vector2D vector) {
		return new Vector2D(this.x - vector.x, this.y - vector.y);
	}
	
	public double area(Vector2D a, Vector2D b)
	{
		Vector2D temp = a.sub(b);
		return this.det(temp) + a.det(b);
	}
	
	/**
	 * Compare l'angle polaire entre les vecteurs
	 * @param a
	 * @param b
	 * @return un booléen indiquant si l'angle polaire est négatif ou non
	 */	
	public boolean angle(Vector2D a, Vector2D b)
	{
		if (this.area(a, b) == 0)
            return this.sub(a).length() < this.sub(b).length();
		
		Vector2D v1 = a.sub(this);
        Vector2D v2 = b.sub(this);
        return (Math.atan2(v1.y, v1.x) - Math.atan2(v2.y, (double) v2.x)) < 0;
	}
	
	/**
	 * Retourne la position d'un vecteur par rapport à un autre
	 * @param vector
	 * @return boolean indiquant si le vecteur vector est à gauche
	 */
	public boolean isLeftTurn(Vector2D vector) {
		
		double det = vector.det(this);
		
		if(det > 0)
			return true;
		else
			return false;
    }
	
	@Override
	public Feature clippingSupport(Vector2D normal) {
		return new Feature(supportVertex(normal));
	}
	
	/**
	 * Implémentation de supportMapping pour Collidable.
	 */
	public Vector2D supportVertex(Vector2D d) {
		return this.clone();
	}
	
	@Override
	public String toString() {
		return new String() + "Vector2D(" + x + ", " + y + ")";
	}
	
	@Override
	public Vector2D transform(AffineTransform transformation) {
		
		Point2D.Double source = new Point2D.Double(x, y);
		Point2D.Double result = new Point2D.Double();
		
		transformation.transform(source, result);
		
		return new Vector2D(result.x, result.y);
	}
}
